In [ ]:
import pandas as pd
import numpy as np
import glob
import matplotlib.pyplot as plt
import random 

Os arquivos devem ser baixados da Justiça Eleitoral: https://dadosabertos.tse.jus.br/dataset/resultados-2022-arquivos-transmitidos-para-totalizacao

Executando o script logjez_process.py arquivos CSV serão gerados para cada estado dois arquivos, um com votos e outro com dados das urnas.

Vamos carregar a tabela de cidades. Só será utilizada para informar o nome da cidade de acordo com o código

In [ ]:
municipios = pd.read_csv("./municipios.csv")
municipios.sample(10)
Out[ ]:
municipio_cod municipio estado
4715 31070 ARAUÁ SE
434 36870 LENÇÓIS BA
2207 90131 ANASTÁCIO MS
27 27057 ARAPIRACA AL
1684 44199 DATAS MG
5565 96830 FÁTIMA TO
3686 16012 ACARI RN
2939 25755 SÃO VICENTE FÉRRER PE
2577 19151 ARAÇAGI PB
1974 49999 PESCADOR MG

Vamos carregar os dados das urnas

In [ ]:
path = "./csv_gerados/??.urnas.csv"
filenames = glob.glob(path)
dfs = []
for filename in filenames:
    dfs.append(pd.read_csv(filename, encoding = "ISO-8859-1"))

urnas = pd.concat(dfs, ignore_index=True).dropna()
In [ ]:
urnas.sample(10)
Out[ ]:
UF municipio zona secao qtdEleitoresAptos qtdComparecimento dataHoraAbertura dataHoraEncerramento idPleito qtdEleitoresLibCodigo qtdEleitoresCompBiometrico modelo
219126 PE 25313 1 281 299 258 20221030T080001 20221030T170046 407 10 245 UE2020
268155 PR 76678 42 319 353 303 20221030T080001 20221030T170108 407 33 303 UE2013
290497 RJ 58998 37 13 291 221 20221030T080001 20221030T170219 407 23 214 UE2015
13800 AM 2550 40 554 402 334 20221030T070001 20221030T160502 407 45 307 UE2009
404660 SP 71072 376 670 367 289 20221030T080001 20221030T181924 407 14 279 UE2015
344096 RS 86193 17 12 356 286 20221030T080001 20221030T170012 407 27 265 UE2011
291189 RJ 58475 254 37 335 270 20221030T080001 20221030T170156 407 23 186 UE2013
344515 SC 82210 106 26 360 281 20221030T080001 20221030T170019 407 20 265 UE2015
74297 CE 14516 14 6 357 278 20221030T080001 20221030T170025 407 39 272 UE2009
5152 AL 27855 3 73 344 285 20221030T080001 20221030T170016 407 18 257 UE2020
In [ ]:
urnas.shape[0]
Out[ ]:
471984

Aqui carregamos os arquivo com os votos.

In [ ]:
path = "./csv_gerados/??.votos.csv"
filenames = glob.glob(path)
dfs = []
for filename in filenames:
    dfs.append(pd.read_csv(filename, encoding = "ISO-8859-1"))

votos = pd.concat(dfs, ignore_index=True).dropna()
In [ ]:
votos.sample(10)
Out[ ]:
UF municipio zona secao cargo quantidadeVotos partido candidato
1182986 RJ 58130 96 10 presidente 145 22 22
1117322 RJ 58637 221 134 presidente 2 nulo nulo
526238 MG 41238 332 202 presidente 132 13 13
1364226 SC 83798 36 38 presidente 1 branco branco
1311180 RS 88366 21 132 presidente 111 13 13
1446496 SP 62910 378 189 presidente 127 22 22
1095861 RJ 60011 21 444 presidente 12 nulo nulo
666786 MG 54097 339 64 presidente 11 nulo nulo
1711410 SP 69671 183 214 presidente 118 13 13
705481 MT 90670 1 1164 presidente 5 nulo nulo
In [ ]:
votos.shape[0]
Out[ ]:
1851420

Precisamos uma tabela com 2 colunas com totais de votos para os candidatos Bolsonaro e Lula

In [ ]:
votosBolsonaro = votos.query("candidato == '22'")
votosLula = votos.query("candidato == '13'")
In [ ]:
votosBolsonaro = votosBolsonaro.rename(columns={"quantidadeVotos":"Bolsonaro"})
votosLula = votosLula.rename(columns={"quantidadeVotos":"Lula"})

Aqui juntamos os dados com votos dos 2 candidatos e o modelo de urna

In [ ]:
votosPresidente = votosBolsonaro[['UF','municipio','zona','secao','Bolsonaro']].join(
    votosLula[['UF','municipio','zona','secao','Lula']]
        .set_index(['UF','municipio','zona','secao']), how="outer",
            on=['UF','municipio','zona','secao']).join(
                urnas[['UF','municipio','zona','secao','modelo']]
                    .set_index(['UF','municipio','zona','secao']), 
                        on=['UF','municipio','zona','secao']
            )
In [ ]:
votosPresidente.head(10)
Out[ ]:
UF municipio zona secao Bolsonaro Lula modelo
1 AC 1015 2 88 84.0 53.0 UE2009
5 AC 1015 2 75 129.0 47.0 UE2009
7 AC 1015 2 69 125.0 53.0 UE2009
11 AC 1015 2 95 133.0 34.0 UE2009
14 AC 1015 2 90 144.0 47.0 UE2009
18 AC 1015 2 73 138.0 45.0 UE2009
22 AC 1015 2 74 129.0 48.0 UE2009
24 AC 1015 2 67 164.0 53.0 UE2009
28 AC 1015 2 80 124.0 98.0 UE2009
32 AC 1015 2 66 162.0 56.0 UE2009
In [ ]:
votosPresidente.shape
Out[ ]:
(471525, 7)

O foco aqui é validar a suspeita de 1 modelo das urnas ter comportamento na totalização de votos, portanto devemos começar analizando como foram distribuidas as urnas, pelo menos por estado, para diminuir a probabiliade de o fator demografico determinar a diferença.

In [ ]:
urnasPorUF = pd.pivot_table(data=votosPresidente[['UF','modelo']], index=['UF'], columns=['modelo'], aggfunc=np.count_nonzero)
ax = urnasPorUF.plot.bar(stacked=True, figsize=(8,6))
ax.set_title('Tipo de Urna Por Estado', fontsize=20)
Out[ ]:
Text(0.5, 1.0, 'Tipo de Urna Por Estado')

Acima vemos a distribuição. Notem que haviam urnas que não encontramos o arquivo de log (SemLog).
O gráfico mostra que as urnas foram distribuidas em todos os estados. Em especial o modelo mais novo que representa uma boa quantidade das urnas para cada estado (em rosa)

Os graficos abaixo mostram a distribuição de votos apurados nas urnas UE2020 e o contraste para as não UE2020 (anteriores a 2020 que não foram auditadas no pleito de 2022)

In [ ]:
data = votosPresidente.query("modelo == 'UE2020'")
plt.scatter(data['Bolsonaro'],data['Lula'], s=1)
plt.title("Urnas UE2020 - Brasil")
plt.xlabel("Bolsonaro")
plt.ylabel("Lula")
plt.show()

data = votosPresidente.query("modelo != 'UE2020'")
plt.scatter(data['Bolsonaro'],data['Lula'], s=1)
plt.title("Urnas Não UE2020 - Brasil")
plt.xlabel("Bolsonaro")
plt.ylabel("Lula")
plt.show()

Os gráficos já mostram acima que o comportamento é muito distinto. As urnas mais antigas deram mais vantagem ao cadidato Lula, aparentemente retirando votos do Bolsonaro.

No gráfico das 2020 aparece um grande numero de votos mais ao centro, enquanto as antigas parecem ter algum tipo de trava, o que gera uma linha geometrica, quase igual um triângulo escaleno.

Vamos ver os mesmos gráficos para cada tipo de urna.

* note que existem 3 populações maiores de urnas, as UE2010, UE2015 e UE2020. Os demais modelos são minoria, o que pode dificultar essa análise de um ponto de vista geometrico

In [ ]:
for index, row in votosPresidente.filter(['modelo'], axis=1).drop_duplicates().sort_values("modelo").iterrows():
    data = votosPresidente.query("modelo == '"+row['modelo']+"'")
    plt.scatter(data['Bolsonaro'],data['Lula'], s=1)
    plt.title("Votos Brasil com urna modelo "+row["modelo"])
    plt.xlabel("Bolsonaro")
    plt.ylabel("Lula")
    plt.show()

Revise os gráficos acima e note como é possível notar o tal triângulo escaleno em todos modelos, menos no modelo UE2020 onde, apesar de visualizarmos o triângulo, há um escape (massa da votos) mais distribuida ao centro.

Isso pode indicar algumas possiveis causas

  • Algum algorítmo foi criado para travar os votos, migrando de outro candidato para o candidato 13
  • Seria improvável os mesmos eleitores, nos mesmos locais, terem comportamento tão diferenciado
  • As urnas tem diferença no software, e o software foi adulterado
  • Aqui vamos explorar as urnas UE2020 e não UE2020 por cada estado

    In [ ]:
    for index, row in votosPresidente.filter(['UF'], axis=1).drop_duplicates().iterrows():
        print(row['UF'])
        data = votosPresidente.query("modelo == 'UE2020' and UF == '"+row['UF']+"'")
        plt.scatter(data['Bolsonaro'],data['Lula'], s=1)
        plt.title("Urnas UE2020 - UF: "+row['UF'])
        plt.xlabel("Bolsonaro")
        plt.ylabel("Lula")
        plt.show()
    
        data = votosPresidente.query("modelo != 'UE2020' and UF == '"+row['UF']+"'")
        plt.scatter(data['Bolsonaro'],data['Lula'], s=1)
        plt.title("Urnas Não UE2020 - UF: "+row['UF'])
        plt.xlabel("Bolsonaro")
        plt.ylabel("Lula")
        plt.show()
    
    AC
    
    AL
    
    AM
    
    AP
    
    BA
    
    CE
    
    DF
    
    ES
    
    GO
    
    MA
    
    MG
    
    MS
    
    MT
    
    PA
    
    PB
    
    PE
    
    PI
    
    PR
    
    RJ
    
    RN
    
    RO
    
    RR
    
    RS
    
    SC
    
    SE
    
    SP
    
    TO
    
    ZZ
    

    Você pode notar nos gráficos acima o achatamento, que fica sempre mais evidente nos estados onde o PT e Lula são mais populares. Nesses estados o achatamento do triângulo fica muito mais evidente.

    Isso demonstra que o algorítimo deve ter alguma lógica, não só aleatória, mas também inteligênte para realizar a migração de votos (fraude) levando em consideração a quantidade de votos de cada candidato adversário do número 13 (número do PT)

    É muito provável que esse algoritmo esteja presente desde quando as urnas começaram a operar, o que pode ter favorecido o candidato a presidente do PT desde então.

    A fraude só pode ser detectada porque as UE2020 não apresentaram o mesmo comportamento, ou o algoritmo falhou nessas urnas, criando a oportunidade de comparar os dados.

    Como será que isso pode refletir nos resultados?
    Vamos analisar isso visualizando os resultados por UF e por tipo de urna

    In [ ]:
    votosPresidente[['UF','Lula','Bolsonaro']].groupby(['UF']).sum(numeric_only=True).plot.bar(color=['red','blue']).set_title("Votos Geral")
    votosPresidente.query("modelo == 'UE2020'")[['UF','Lula','Bolsonaro']].groupby(['UF']).sum(numeric_only=True).plot.bar(color=['red','blue']).set_title("Votos UE2020")
    votosPresidente.query("modelo != 'UE2020'")[['UF','Lula','Bolsonaro']].groupby(['UF']).sum(numeric_only=True).plot.bar(color=['red','blue']).set_title("Votos Não UE2020")
    
    Out[ ]:
    Text(0.5, 1.0, 'Votos Não UE2020')
    In [ ]:
    votosPresidente[['UF','modelo','Lula','Bolsonaro']].groupby(['modelo']).sum(numeric_only=True).plot.bar(color=['red','blue']).set_title("Votos por Tipo de Urna")
    
    Out[ ]:
    Text(0.5, 1.0, 'Votos por Tipo de Urna')
    In [ ]:
    votosPresidente.query("UF == 'BA' and modelo == 'UE2020'")
    
    Out[ ]:
    UF municipio zona secao Bolsonaro Lula modelo
    69827 BA 35173 137 9 73.0 179.0 UE2020
    69831 BA 35173 137 1 79.0 161.0 UE2020
    69835 BA 35173 137 7 82.0 191.0 UE2020
    69839 BA 35173 137 12 51.0 129.0 UE2020
    69843 BA 35173 137 3 38.0 71.0 UE2020
    ... ... ... ... ... ... ... ...
    203224 BA 37370 92 42 79.0 141.0 UE2020
    203228 BA 37370 92 48 86.0 130.0 UE2020
    203232 BA 37370 92 45 97.0 120.0 UE2020
    203236 BA 37370 92 68 94.0 152.0 UE2020
    1851417 BA 38490 5 800 NaN 80.0 UE2020

    13050 rows × 7 columns